home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / MOUNT.PY < prev    next >
Encoding:
Python Source  |  2000-09-21  |  12.7 KB  |  333 lines

  1. ##############################################################################
  2. # Zope Public License (ZPL) Version 1.0
  3. # -------------------------------------
  4. # Copyright (c) Digital Creations.  All rights reserved.
  5. # This license has been certified as Open Source(tm).
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Digital Creations requests that attribution be given to Zope
  16. #    in any manner possible. Zope includes a "Powered by Zope"
  17. #    button that is installed by default. While it is not a license
  18. #    violation to remove this button, it is requested that the
  19. #    attribution remain. A significant investment has been put
  20. #    into Zope, and this effort will continue if the Zope community
  21. #    continues to grow. This is one way to assure that growth.
  22. # 4. All advertising materials and documentation mentioning
  23. #    features derived from or use of this software must display
  24. #    the following acknowledgement:
  25. #      "This product includes software developed by Digital Creations
  26. #      for use in the Z Object Publishing Environment
  27. #      (http://www.zope.org/)."
  28. #    In the event that the product being advertised includes an
  29. #    intact Zope distribution (with copyright and license included)
  30. #    then this clause is waived.
  31. # 5. Names associated with Zope or Digital Creations must not be used to
  32. #    endorse or promote products derived from this software without
  33. #    prior written permission from Digital Creations.
  34. # 6. Modified redistributions of any form whatsoever must retain
  35. #    the following acknowledgment:
  36. #      "This product includes software developed by Digital Creations
  37. #      for use in the Z Object Publishing Environment
  38. #      (http://www.zope.org/)."
  39. #    Intact (re-)distributions of any official Zope release do not
  40. #    require an external acknowledgement.
  41. # 7. Modifications are encouraged but must be packaged separately as
  42. #    patches to official Zope releases.  Distributions that do not
  43. #    clearly separate the patches from the original work must be clearly
  44. #    labeled as unofficial distributions.  Modifications which do not
  45. #    carry the name Zope may be packaged in any form, as long as they
  46. #    conform to all of the clauses above.
  47. # Disclaimer
  48. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  49. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  51. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  52. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  53. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  54. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  55. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  56. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  57. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  58. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  59. #   SUCH DAMAGE.
  60. # This software consists of contributions made by Digital Creations and
  61. # many individuals on behalf of Digital Creations.  Specific
  62. # attributions are listed in the accompanying credits file.
  63. ##############################################################################
  64. """Mounted database support
  65.  
  66. $Id: Mount.py,v 1.4.2.2 2000/09/21 21:35:58 shane Exp $"""
  67. __version__='$Revision: 1.4.2.2 $'[11:-2]
  68.  
  69. import Globals, thread, Persistence, Acquisition
  70. import ExtensionClass, string, time, sys
  71. from POSException import MountedStorageError
  72. from zLOG import LOG, ERROR, INFO, WARNING
  73.  
  74. # dbs is a holder for all DB objects, needed to overcome
  75. # threading issues.  It maps connection params to a DB object
  76. # and a mapping of mount points.
  77. dbs = {}
  78.  
  79. # dblock is locked every time dbs is accessed.
  80. dblock=thread.allocate_lock()
  81.  
  82. try:
  83.     # Make special provisions for ZClasses if we're in a Zope
  84.     # installation.
  85.     from Zope.ClassFactory import ClassFactory
  86.     
  87.     def RootDefsClassFactory(jar, module, name):
  88.         # Use the class definitions given at
  89.         # the root of the Zope installation.
  90.         while hasattr(jar, '_mount_parent_jar'):
  91.             jar = jar._mount_parent_jar
  92.         return ClassFactory(jar, module, name)
  93. except:
  94.     ClassFactory = None
  95.     RootDefsClassFactory = None
  96.  
  97.  
  98. class MountPoint(Persistence.Persistent, Acquisition.Implicit):
  99.     '''The base class for a Zope object which, when traversed,
  100.     accesses a different database.
  101.     '''
  102.  
  103.     # Default values for non-persistent variables.
  104.     _v_db = None
  105.     _v_data = None
  106.     _v_close_db = 0
  107.     _v_connect_error = None
  108.  
  109.     def __init__(self, path, params=None, classDefsFromRoot=1):
  110.         '''
  111.         @arg path The path within the mounted database from which
  112.         to derive the root.
  113.  
  114.         @arg params The parameters used to connect to the database.
  115.         No particular format required.
  116.         If there is more than one mount point referring to a
  117.         database, MountPoint will detect the matching params
  118.         and use the existing database.  Include the class name of
  119.         the storage.  For example,
  120.         ZEO params might be "ZODB.ZEOClient localhost 1081".
  121.  
  122.         @arg classDefsFromRoot If true (the default), MountPoint will
  123.         try to get ZClass definitions from the root database rather
  124.         than the mounted database.
  125.         '''
  126.         # The only reason we need a __mountpoint_id is to
  127.         # be sure we don't close a database prematurely when
  128.         # it is mounted more than once and one of the points
  129.         # is unmounted.
  130.         self.__mountpoint_id = '%s_%f' % (id(self), time.time())
  131.         if params is None:
  132.             # We still need something to use as a hash in
  133.             # the "dbs" dictionary.
  134.             params = self.__mountpoint_id
  135.         self._params = repr(params)
  136.         self._path = path
  137.         self._classDefsFromRoot = classDefsFromRoot
  138.  
  139.     def _createDB(self):
  140.         '''Gets the database object, usually by creating a Storage object
  141.         and returning ZODB.DB(storage).
  142.         '''
  143.         raise 'NotImplemented'
  144.  
  145.     def _getDB(self):
  146.         '''Creates or opens a DB object.
  147.         '''
  148.         newMount = 0
  149.         dblock.acquire()
  150.         try:
  151.             params = self._params
  152.             dbInfo = dbs.get(params, None)
  153.             if dbInfo is None:
  154.                 LOG('ZODB', INFO, 'Opening database for mounting: %s' % params)
  155.                 db = self._createDB()
  156.                 newMount = 1
  157.                 dbs[params] = (db, {self.__mountpoint_id:1})
  158.  
  159.                 if RootDefsClassFactory is not None and \
  160.                    getattr(self, '_classDefsFromRoot', 1):
  161.                     db.setClassFactory(RootDefsClassFactory)
  162.                 elif ClassFactory is not None:
  163.                     db.setClassFactory(ClassFactory)
  164.             else:
  165.                 db, mounts = dbInfo
  166.                 # Be sure this object is in the list of mount points.
  167.                 if not mounts.has_key(self.__mountpoint_id):
  168.                     newMount = 1
  169.                     mounts[self.__mountpoint_id] = 1
  170.             self._v_db = db
  171.         finally:
  172.             dblock.release()
  173.         return db, newMount
  174.  
  175.     def __repr__(self):
  176.         return "%s %s" % (self.__class__.__name__, self._path)
  177.  
  178.     def _close(self):
  179.         # The onCloseCallback handler.
  180.         # Closes a single connection to the database
  181.         # and possibly the database itself.
  182.         t = self._v_data
  183.         if t is not None:
  184.             self._v_data = None
  185.             data = t[0]
  186.             if getattr(data, '_v__object_deleted__', 0):
  187.                 # This mount point has been deleted.
  188.                 del data._v__object_deleted__
  189.                 self._v_close_db = 1
  190.             if data is not None:
  191.                 conn = data._p_jar
  192.                 if conn is not None:
  193.                     try: del conn._mount_parent_jar
  194.                     except: pass
  195.                     if conn._db is not None:
  196.                         conn.close()
  197.         if self._v_close_db:
  198.             # Stop using this database. Close it if no other
  199.             # MountPoint is using it.
  200.             dblock.acquire()
  201.             try:
  202.                 self._v_close_db = 0
  203.                 self._v_db = None
  204.                 params = self._params
  205.                 if dbs.has_key(params):
  206.                     dbInfo = dbs[params]
  207.                     db, mounts = dbInfo
  208.                     try: del mounts[self.__mountpoint_id]
  209.                     except: pass
  210.                     if len(mounts) < 1:
  211.                         # No more mount points are using this database.
  212.                         del dbs[params]
  213.                         db.close()
  214.                         LOG('ZODB', INFO, 'Closed database: %s' % params)
  215.             finally:
  216.                 dblock.release()
  217.  
  218.     def __openConnection(self, parent):
  219.         # Opens a new connection to the database.
  220.         db = self._v_db
  221.         if db is None:
  222.             self._v_close_db = 0
  223.             db, newMount = self._getDB()
  224.         else:
  225.             newMount = 0
  226.         jar = getattr(self, '_p_jar', None)
  227.         if jar is None:
  228.             # Get _p_jar from parent.
  229.             self._p_jar = jar = parent._p_jar
  230.         conn = db.open(version=jar.getVersion())
  231.         # Add an attribute to the connection which
  232.         # makes it possible for us to find the primary
  233.         # database connection.  See ClassFactoryForMount().
  234.         conn._mount_parent_jar = jar
  235.  
  236.         try:
  237.             jar.onCloseCallback(self._close)
  238.             obj = self._getMountRoot(conn.root())
  239.             data = getattr(obj, 'aq_base', obj)
  240.             # Store the data object in a tuple to hide from acquisition.
  241.             self._v_data = (data,)
  242.         except:
  243.             # Close the connection before processing the exception.
  244.             del conn._mount_parent_jar
  245.             conn.close()
  246.             raise
  247.         if newMount:
  248.             id = data.id
  249.             if callable(id):
  250.                 id = id()
  251.             p = string.join(parent.getPhysicalPath() + (id,), '/')
  252.             LOG('ZODB', INFO, 'Mounted database %s at %s' % \
  253.                 (self._params, p))
  254.         return data
  255.  
  256.     def __of__(self, parent):
  257.         # Accesses the database, returning an acquisition
  258.         # wrapper around the connected object rather than around self.
  259.         t = self._v_data
  260.         if t is None:
  261.             self._v_connect_error = None
  262.             try:
  263.                 data = self.__openConnection(parent)
  264.             except:
  265.                 self._v_close_db = 1
  266.                 self._logConnectException()
  267.                 # Broken database. Wrap around self.
  268.                 return Acquisition.ImplicitAcquisitionWrapper(self, parent)
  269.         else:
  270.             data = t[0]
  271.  
  272.         return data.__of__(parent)
  273.  
  274.     def _test(self, parent):
  275.         '''Tests the database connection.
  276.         '''
  277.         if self._v_data is None:
  278.             try:
  279.                 data = self.__openConnection(parent)
  280.             except:
  281.                 self._v_close_db = 1
  282.                 self._logConnectException()
  283.                 raise
  284.         return 1
  285.  
  286.     def _getMountRoot(self, root):
  287.         '''Gets the object to be mounted.
  288.         Can be overridden to provide different behavior.
  289.         '''
  290.         try:
  291.             app = root['Application']
  292.         except:
  293.             raise MountedStorageError, \
  294.                   'No \'Application\' object exists in the mountable database.'
  295.         try:
  296.             return app.unrestrictedTraverse(self._path)
  297.         except:
  298.             raise MountedStorageError, \
  299.                   ('The path \'%s\' was not found in the mountable database.' \
  300.                    % self._path)
  301.  
  302.     def _logConnectException(self):
  303.         '''Records info about the exception that just occurred.
  304.         '''
  305.         from cStringIO import StringIO
  306.         import traceback
  307.         exc = sys.exc_info()
  308.         LOG('ZODB', WARNING, 'Failed to mount database. %s (%s)' % exc[:2])
  309.         f=StringIO()
  310.         traceback.print_tb(exc[2], 100, f)
  311.         self._v_connect_error = (exc[0], exc[1], f.getvalue())
  312.